package com.dc.dbus.session.client;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

import com.dc.dbus.session.RC;
import com.dc.dbus.session.bean.FutureResult;
import com.dc.dbus.session.client.bean.AsyncInfo;
import com.dc.dbus.session.client.bean.SyncInfo;
import com.dc.dbus.session.exception.RemoteException;
import com.dc.dbus.session.exception.RemoteExceptionWrapper;
import com.dc.dbus.transport.commons.ClientSideTransport;

public class ClientStub implements InvocationHandler {
	
	protected final String name;
	
	protected final AtomicLong msgIdGen;
	
	protected final ClientSideTransport clientSideTansport;
	
	protected final ClientInvokeFeedback clientInvokeFeedback;
	
    protected final HashMap<String, MethodInfo> methodNameMapInfo;
    
    public ClientStub(String serviceName, AtomicLong msgIdGen,
    		ClientSideTransport clientSideTansport, ClientInvokeFeedback clientInvokeFeedback,
    		HashMap<String, MethodInfo> methodNameMapInfo) {
    	this.name = serviceName;
    	this.msgIdGen = msgIdGen;
    	this.clientSideTansport = clientSideTansport;
    	this.clientInvokeFeedback = clientInvokeFeedback;
        this.methodNameMapInfo = methodNameMapInfo;
    }

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    	MethodInfo methodInfo = methodNameMapInfo.get(method.getName());

    	String router;
    	if (methodInfo.paramRouterIndex != -1) {
    		router = (String) args[methodInfo.paramRouterIndex];
    	} else {
    		router = methodInfo.defaultRouter;
    	}
    	
    	switch (methodInfo.reqType) {
		case SYNC:
			return invokeSync(router, methodInfo.timeout, methodInfo.cmd, args);
			
		case ASYNC:
			return invokeAsync(router, methodInfo.timeout, methodInfo.cmd, args);
		case MSG:
		default:
			msg(router, methodInfo.cmd, args);
			return null;
		}
    	
    }
	
	protected void msg(String router, int cmd, Object param) {
		
		long msgId = msgIdGen.incrementAndGet();
		clientSideTansport.send(name, router, msgId, cmd, param, 0);
	}
	
	@SuppressWarnings("rawtypes")
	protected FutureResult invokeAsync(String router, int timeout, int cmd, Object param) {
		
		long msgId = msgIdGen.incrementAndGet();
		
		AsyncInfo async = clientInvokeFeedback.asyncInvoke(msgId, timeout);
		
		clientSideTansport.send(name, router, msgId, cmd, param, timeout);
		
		return async.future;
	}
	
	protected Object invokeSync(String router, int timeout, int cmd, Object param) throws Throwable {
		
		long msgId = msgIdGen.incrementAndGet();
		
		SyncInfo sync = clientInvokeFeedback.syncInvoke(msgId);
		
		ReentrantLock lock = sync.lock;
		lock.lock();
		try {

			clientSideTansport.send(name, router, msgId, cmd, param, timeout);
			
			if (!sync.condition.await(timeout, TimeUnit.MILLISECONDS)) {
				clientInvokeFeedback.syncInvokeTimeOut(msgId);
				throw new RemoteException(RC.TIME_OUT, "invoke time out, for (msgId:" + msgId 
						+ ", cmd:" + cmd + ", param:" + paramToString(param) + ")");
			}
			
			if (sync.resultCode != RC.SUCCESS) {
				if (sync.result == null) {
					throw new RemoteException(sync.resultCode);
				} else {
					throw new RemoteExceptionWrapper((Throwable) sync.result);
				}
			}
			
			return sync.result;
			
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		} finally {
			lock.unlock();
		}
	}
	
	protected String paramToString(Object param) {
		
		if (param instanceof Object[]) {
			return Arrays.toString((Object[])param);
		} else {
			return param.toString();
		}
	}
}
